import datetime
import json
import re
from common.settings import logger
from common.constant import ApiInfo, AssertType, AssertHandlerInfo, MysqlConnectInfo
from typing import Union, List
from jsonpath import jsonpath
from utils.dbUtils.mysql_control import MysqlHandler
from utils.readFileUtils.yaml_control import YamlHandler


class AssertHandler:

    def __init__(self, api_info: dict, actual_result: str, sql: str, check_point: dict):
        self.api_info = api_info
        self.actual_result = actual_result
        self.sql = sql
        self.check_point = check_point

    @property
    def assert_info(self) -> Union[dict, None]:
        return self.api_info.get(ApiInfo.ASSERT_INFO.value)

    @property
    def json_full_text(self) -> Union[str, None]:
        return self.check_point.get(AssertType.JSON_FULL_TEXT.value)

    @property
    def gte_amount(self) -> Union[int, None]:
        return self.check_point.get(AssertType.GTE_AMOUNT.value)

    @property
    def equal_amount(self) -> Union[int, None]:
        return self.check_point.get(AssertType.EQUAL_AMOUNT.value)

    @property
    def contain_fields(self) -> Union[str, None]:
        return self.check_point.get(AssertType.CONTAIN_FIELDS.value)

    @property
    def equal_field(self) -> Union[str, None]:
        return self.check_point.get(AssertType.EQUAL_FIELD.value)

    @property
    def sql_result(self) -> Union[str, None]:
        return self.check_point.get(AssertType.SQL_RESULT.value)

    def is_check_point_empty(self):
        if not (self.json_full_text or self.gte_amount or self.equal_amount or self.contain_fields or self.equal_field
                or self.sql_result):
            logger.error(AssertHandlerInfo.EXPECTED_VALUE_IS_NONE.value)
            raise ValueError(AssertHandlerInfo.EXPECTED_VALUE_IS_NONE.value)

    @staticmethod
    def remove_response_field(response: dict, ignore_json_path: str) -> dict:
        # ignore_json_path 必须以"$."开始，后面跟着一系列由字母和数字组成的子字符串，并由"."分隔
        pattern = r'^\$\.[a-zA-Z0-9]+(\.[a-zA-Z0-9]+)*$'
        if re.match(pattern, ignore_json_path):
            ignore_json_path = ignore_json_path.split('.')
            key = ''
            for path in ignore_json_path[1:]:
                key += f"['{path}']"
            del_statement = f"del(response{key})"
            exec(del_statement)
            return response
        else:
            logger.error(f'ignoreResponseKey: {ignore_json_path}有误')
            raise ValueError(f'ignoreResponseKey: {ignore_json_path}有误')

    def json_full_text_compare(self) -> None:
        actual_response = json.loads(self.actual_result)
        expected_response = json.loads(self.json_full_text)

        # 用于删除一些每次请求都会发生变化的响应值，如：traceId、时间戳等
        ignore_field = self.assert_info.get(ApiInfo.JSON_FULL_TEXT_IGNORE_FIELD.value)
        if ignore_field:
            actual_response = self.remove_response_field(actual_response, ignore_field)
            expected_response = self.remove_response_field(expected_response, ignore_field)

        assert actual_response == expected_response, logger.error(AssertHandlerInfo.JSON_FULL_TEXT_FAIL.value)
        logger.info(AssertHandlerInfo.JSON_FULL_TEXT_PASS.value)

    def get_json_path_value(self, key: str, assert_type: str) -> Union[List[str], str, int]:
        json_path = self.assert_info.get(key)
        if json_path:
            # 校验包含指定字段的值，获取jsonpath对应的列表，获取列表中所有指定字段，把它们添加到一个列表中并返回
            if assert_type == AssertType.CONTAIN_FIELDS.value:
                value_lst = []
                json_path_and_field = json_path.split(',')
                if len(json_path_and_field) > 2:
                    logger.error(AssertHandlerInfo.ONLY_SUPPORT_ONE_FIELD.value)
                    raise ValueError(AssertHandlerInfo.ONLY_SUPPORT_ONE_FIELD.value)
                json_path, field = json_path_and_field
                # 删除空格
                field = field.strip()
                res = jsonpath(json.loads(self.actual_result), json_path)
                for item in res[0]:
                    value = item.get(field)
                    if not isinstance(value, str):
                        value = str(value)
                    value_lst.append(value)
                return value_lst
            else:
                res = jsonpath(json.loads(self.actual_result), json_path)
                # 校验等于指定字段的值
                if assert_type == AssertType.EQUAL_FIELD.value:
                    value = res[0]
                    if not isinstance(value, str):
                        value = str(value)
                    return value
                # 校验数据条数，获取jsonpath对应的列表，返回它的长度
                elif assert_type in (AssertType.GTE_AMOUNT.value, AssertType.EQUAL_AMOUNT.value):
                    return len(res[0])
        else:
            logger.error(f'{key}为空')
            raise ValueError(f'{key}为空')

    def gte_amount_compare(self) -> None:
        length = self.get_json_path_value(ApiInfo.LIST_JSON_PATH.value, AssertType.GTE_AMOUNT.value)
        assert length >= self.gte_amount, logger.error(AssertHandlerInfo.GTE_AMOUNT_FAIL.value)
        logger.info(AssertHandlerInfo.GTE_AMOUNT_PASS.value)

    def equal_amount_compare(self) -> None:
        length = self.get_json_path_value(ApiInfo.LIST_JSON_PATH.value, AssertType.EQUAL_AMOUNT.value)
        assert length == self.equal_amount, logger.error(AssertHandlerInfo.EQUAL_AMOUNT_FAIL.value)
        logger.info(AssertHandlerInfo.EQUAL_AMOUNT_PASS.value)

    def contain_fields_compare(self) -> None:
        value_lst = self.get_json_path_value(ApiInfo.CONTAIN_FIELDS_JSON_PATH.value, AssertType.CONTAIN_FIELDS.value)
        # excel中填写了多个值
        if ',' in self.contain_fields:
            fields_lst = self.contain_fields.split(',')
            assert set(fields_lst).issubset(set(value_lst)), logger.error(AssertHandlerInfo.CONTAIN_FIELDS_FAIL.value)
            logger.info(AssertHandlerInfo.CONTAIN_FIELDS_PASS.value)
        # excel中填写了单个值
        else:
            assert self.contain_fields in value_lst, logger.error(AssertHandlerInfo.CONTAIN_FIELDS_FAIL.value)
            logger.info(AssertHandlerInfo.CONTAIN_FIELDS_PASS.value)

    def equal_field_compare(self) -> None:
        value = self.get_json_path_value(ApiInfo.EQUAL_FIELD_JSON_PATH.value, AssertType.EQUAL_FIELD.value)
        assert self.equal_field == value, logger.error(AssertHandlerInfo.EQUAL_FIELD_FAIL.value)
        logger.info(AssertHandlerInfo.EQUAL_FIELD_PASS.value)

    def sql_result_compare(self) -> None:
        db_name = self.assert_info.get(ApiInfo.DB_NAME.value)
        if db_name:
            db_info = YamlHandler.read_yaml_file('common/db_info.yaml')
            if db_name in db_info.keys():
                mysql_handler = MysqlHandler(host=db_info[db_name][MysqlConnectInfo.HOST.value],
                                             port=db_info[db_name][MysqlConnectInfo.PORT.value],
                                             user=db_info[db_name][MysqlConnectInfo.USER.value],
                                             password=db_info[db_name][MysqlConnectInfo.PASSWORD.value],
                                             database=db_info[db_name][MysqlConnectInfo.DATABASE.value])
                if self.sql:
                    result = mysql_handler.get_one(self.sql)
                    for key in result.keys():
                        if isinstance(result[key], datetime.datetime):
                            result[key] = str(result[key])
                    logger.info(f'sql执行结果：{result}')
                    assert json.loads(self.sql_result) == result, logger.error(AssertHandlerInfo.SQL_RESULT_FAIl.value)
                    logger.info(AssertHandlerInfo.SQL_RESULT_PASS.value)
                else:
                    logger.error(AssertHandlerInfo.SQL_IS_NONE.value)
                    raise ValueError(AssertHandlerInfo.SQL_IS_NONE.value)
            else:
                logger.error(AssertHandlerInfo.DB_NAME_NOT_EXIST.value)
                raise ValueError(AssertHandlerInfo.DB_NAME_NOT_EXIST.value)
        else:
            logger.error(AssertHandlerInfo.DB_NAME_IS_NONE.value)
            raise ValueError(AssertHandlerInfo.DB_NAME_IS_NONE.value)

    def run(self):
        if self.json_full_text:
            self.json_full_text_compare()
        if self.gte_amount:
            self.gte_amount_compare()
        if self.equal_amount:
            self.equal_amount_compare()
        if self.contain_fields:
            self.contain_fields_compare()
        if self.equal_field:
            self.equal_field_compare()
        if self.sql_result:
            self.sql_result_compare()
        self.is_check_point_empty()
